package com.wuhao.code.check

import com.aegis.mybatis.xmlless.model.base.BaseAnnotatedElementWrapper
import com.intellij.lang.jvm.JvmAnnotatedElement
import com.intellij.lang.jvm.JvmAnnotation
import com.intellij.lang.jvm.annotation.*
import com.intellij.psi.PsiAnnotation
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiField
import com.intellij.psi.PsiParameter
import com.intellij.psi.impl.source.PsiClassReferenceType
import com.wuhao.code.check.model.KtParameterWrapper
import com.wuhao.code.check.model.KtPropertyWrapper
import com.wuhao.code.check.model.PsiFieldWrapper
import com.wuhao.code.check.model.PsiParameterWrapper
import org.jetbrains.kotlin.asJava.classes.KtUltraLightElementWithNullabilityAnnotationDescriptorBased
import org.jetbrains.kotlin.psi.KtAnnotated
import org.jetbrains.kotlin.psi.KtParameter
import org.jetbrains.kotlin.psi.KtProperty
import kotlin.reflect.KProperty

/**
 *
 * @author 吴昊
 * @date 2024/1/24 9:45
 * @since v0.0.0
 * @version 1.0
 */
fun JvmAnnotatedElement.getJavaElStringListAnnotationValue(
    annoClass: String,
    propertyName: String
): List<String> {
  val anno = this.getAnnotation(annoClass)
  anno?.attributes?.forEach {
    if (it.attributeName == propertyName) {
      val exp = it.attributeValue
      if (exp is JvmAnnotationArrayValue) {
        return exp.values.map { expression ->
          expression.toStringValue()
        }
      } else if (exp != null) {
        return listOf(exp.toStringValue())
      }
    }
  }
  return listOf()
}

fun JvmAnnotationAttributeValue.toStringValue(): String {
  if (this is JvmAnnotationConstantValue) {
    return this.constantValue.toString()
  }
  if (this is JvmAnnotationEnumFieldValue) {
    return this.fieldName ?: ""
  }
  return this.toString()
}

/**
 * @param annoClass
 * @param property
 * @return
 */
fun PsiElement.getStringAnnoValue(annoClass: Class<*>, property: KProperty<*>): String? {
  return this.getStringAnnoValue(annoClass.name, property.name)
}

fun PsiElement.getStringAnnoValue(annoClass: String, propertyName: String = "value"): String? {
  return when (this) {
    is KtAnnotated         -> {
      getStringAnnoValue(this, annoClass, propertyName)
    }

    is JvmAnnotatedElement -> {
      val valueExpression = this.getAnnotationPropertyValue(annoClass, propertyName) ?: return null
      return valueExpression.toStringValue()
    }

    is PsiAnnotation -> {
      return this.getAttributeValue(propertyName)?.toStringValue()
    }

    else                   -> {
      error("unsupported element type")
    }
  }
}

/**
 * @param annoClass
 * @param propertyName
 * @return
 */
fun JvmAnnotatedElement.getAnnotationPropertyValue(
    annoClass: String,
    propertyName: String
): JvmAnnotationAttributeValue? {
  val anno = this.getAnnotation(annoClass) ?: return null
  return anno.getAttributeValue(propertyName)
}

fun JvmAnnotation.getAttributeValue(
    propertyName: String
): JvmAnnotationAttributeValue? {
  return this.attributes.find {
    it.attributeName == propertyName
  }?.attributeValue
}

fun resolvePsiClass(value: JvmAnnotationAttributeValue?): PsiClass? {
  if (value is PsiClassReferenceType) {
    val clazz = value.reference.resolve()
    if (clazz is PsiClass) {
      return clazz
    }
  }
  if (value is JvmAnnotationClassValue) {
    val clazz = value.clazz
    if (clazz is PsiClass) {
      return clazz
    }
  }
  return null
}

fun PsiElement.toAnnotationElementWrapper(): BaseAnnotatedElementWrapper {
  return when (this) {
    is KtProperty                                                        -> KtPropertyWrapper(this)
    is KtParameter                                                       -> KtParameterWrapper(this)
    is PsiField                                                          -> PsiFieldWrapper(this)
    is PsiParameter                                                      -> PsiParameterWrapper(this)
    is KtUltraLightElementWithNullabilityAnnotationDescriptorBased<*, *> -> {
      this.kotlinOrigin?.toAnnotationElementWrapper() ?: error("无法识别的字段类型${this}")
    }

    else                                                                 -> error("无法识别的字段类型${this}")
  }
}
